package gagak.framework.web.support.http;

import gagak.framework.web.paramresovler.requestbody.LinkedCaseInsensitiveMap;
import gagak.framework.web.paramresovler.requestbody.MultiValueMap;
import gagak.framework.web.annonation.HttpMethod;
import gagak.framework.web.utils.Assert;
import gagak.framework.web.utils.StringUtils;

import java.io.Serializable;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.URI;
import java.nio.charset.Charset;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/*
 * @author houhong
 */
public class HttpHeaders implements MultiValueMap<String, String>, Serializable {

    private static final long serialVersionUID = -8578554704772377436L;


    /*
     * The HTTP {@code Accept} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.2">
     */
    public static final String ACCEPT = "Accept";
    /*
     * The HTTP {@code Accept-Charset} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.3">Section 5.3.3 of RFC 7231</a>
     */
    public static final String ACCEPT_CHARSET = "Accept-Charset";
    /*
     * The HTTP {@code Accept-Encoding} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.4">Section 5.3.4 of RFC 7231</a>
     */
    public static final String ACCEPT_ENCODING = "Accept-Encoding";
    /*
     * The HTTP {@code Accept-Language} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.3.5">Section 5.3.5 of RFC 7231</a>
     */
    public static final String ACCEPT_LANGUAGE = "Accept-Language";
    /*
     * The HTTP {@code Accept-Ranges} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-2.3">Section 5.3.5 of RFC 7233</a>
     */
    public static final String ACCEPT_RANGES = "Accept-Ranges";
    /*
     * The CORS {@code Access-Control-Allow-Credentials} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_CREDENTIALS = "Access-Control-Allow-Credentials";
    /*
     * The CORS {@code Access-Control-Allow-Headers} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_HEADERS = "Access-Control-Allow-Headers";
    /*
     * The CORS {@code Access-Control-Allow-Methods} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_METHODS = "Access-Control-Allow-Methods";
    /*
     * The CORS {@code Access-Control-Allow-Origin} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_ALLOW_ORIGIN = "Access-Control-Allow-Origin";
    /*
     * The CORS {@code Access-Control-Expose-Headers} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_EXPOSE_HEADERS = "Access-Control-Expose-Headers";
    /*
     * The CORS {@code Access-Control-Max-Age} response header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_MAX_AGE = "Access-Control-Max-Age";
    /*
     * The CORS {@code Access-Control-Request-Headers} request header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_REQUEST_HEADERS = "Access-Control-Request-Headers";
    /*
     * The CORS {@code Access-Control-Request-Method} request header field name.
     *
     * @see <a href="https://www.w3.org/TR/cors/">CORS W3C recommendation</a>
     */
    public static final String ACCESS_CONTROL_REQUEST_METHOD = "Access-Control-Request-Method";
    /*
     * The HTTP {@code Age} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.1">Section 5.1 of RFC 7234</a>
     */
    public static final String AGE = "Age";
    /*
     * The HTTP {@code Allow} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.1">Section 7.4.1 of RFC 7231</a>
     */
    public static final String ALLOW = "Allow";
    /*
     * The HTTP {@code Authorization} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.2">Section 4.2 of RFC 7235</a>
     */
    public static final String AUTHORIZATION = "Authorization";
    /*
     * The HTTP {@code Cache-Control} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.2">Section 5.2 of RFC 7234</a>
     */
    public static final String CACHE_CONTROL = "Cache-Control";
    /*
     * The HTTP {@code Connection} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.1">Section 6.1 of RFC 7230</a>
     */
    public static final String CONNECTION = "Connection";
    /*
     * The HTTP {@code Content-Encoding} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.2.2">Section 3.1.2.2 of RFC 7231</a>
     */
    public static final String CONTENT_ENCODING = "Content-Encoding";
    /*
     * The HTTP {@code Content-Disposition} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6266">RFC 6266</a>
     */
    public static final String CONTENT_DISPOSITION = "Content-Disposition";
    /*
     * The HTTP {@code Content-Language} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.3.2">Section 3.1.3.2 of RFC 7231</a>
     */
    public static final String CONTENT_LANGUAGE = "Content-Language";
    /*
     * The HTTP {@code Content-Length} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.2">Section 3.3.2 of RFC 7230</a>
     */
    public static final String CONTENT_LENGTH = "Content-Length";
    /*
     * The HTTP {@code Content-Location} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.4.2">Section 3.1.4.2 of RFC 7231</a>
     */
    public static final String CONTENT_LOCATION = "Content-Location";
    /*
     * The HTTP {@code Content-Range} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-4.2">Section 4.2 of RFC 7233</a>
     */
    public static final String CONTENT_RANGE = "Content-Range";
    /*
     * The HTTP {@code Content-Type} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-3.1.1.5">Section 3.1.1.5 of RFC 7231</a>
     */
    public static final String CONTENT_TYPE = "Content-Type";
    /*
     * The HTTP {@code Cookie} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.3.4">Section 4.3.4 of RFC 2109</a>
     */
    public static final String COOKIE = "Cookie";
    /*
     * The HTTP {@code Date} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.1.2">Section 7.1.1.2 of RFC 7231</a>
     */
    public static final String DATE = "Date";
    /*
     * The HTTP {@code ETag} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.3">Section 2.3 of RFC 7232</a>
     */
    public static final String ETAG = "ETag";
    /*
     * The HTTP {@code Expect} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.1">Section 5.1.1 of RFC 7231</a>
     */
    public static final String EXPECT = "Expect";
    /*
     * The HTTP {@code Expires} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.3">Section 5.3 of RFC 7234</a>
     */
    public static final String EXPIRES = "Expires";
    /*
     * The HTTP {@code From} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.1">Section 5.5.1 of RFC 7231</a>
     */
    public static final String FROM = "From";
    /*
     * The HTTP {@code Host} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.4">Section 5.4 of RFC 7230</a>
     */
    public static final String HOST = "Host";
    /*
     * The HTTP {@code If-Match} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.1">Section 3.1 of RFC 7232</a>
     */
    public static final String IF_MATCH = "If-Match";
    /*
     * The HTTP {@code If-Modified-Since} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.3">Section 3.3 of RFC 7232</a>
     */
    public static final String IF_MODIFIED_SINCE = "If-Modified-Since";
    /*
     * The HTTP {@code If-None-Match} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.2">Section 3.2 of RFC 7232</a>
     */
    public static final String IF_NONE_MATCH = "If-None-Match";
    /*
     * The HTTP {@code If-Range} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.2">Section 3.2 of RFC 7233</a>
     */
    public static final String IF_RANGE = "If-Range";
    /*
     * The HTTP {@code If-Unmodified-Since} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-3.4">Section 3.4 of RFC 7232</a>
     */
    public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since";
    /*
     * The HTTP {@code Last-Modified} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7232#section-2.2">Section 2.2 of RFC 7232</a>
     */
    public static final String LAST_MODIFIED = "Last-Modified";
    /*
     * The HTTP {@code Link} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc5988">RFC 5988</a>
     */
    public static final String LINK = "Link";
    /*
     * The HTTP {@code Location} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.2">Section 7.1.2 of RFC 7231</a>
     */
    public static final String LOCATION = "Location";
    /*
     * The HTTP {@code Max-Forwards} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.1.2">Section 5.1.2 of RFC 7231</a>
     */
    public static final String MAX_FORWARDS = "Max-Forwards";
    /*
     * The HTTP {@code Origin} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc6454">RFC 6454</a>
     */
    public static final String ORIGIN = "Origin";
    /*
     * The HTTP {@code Pragma} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.4">Section 5.4 of RFC 7234</a>
     */
    public static final String PRAGMA = "Pragma";
    /*
     * The HTTP {@code Proxy-Authenticate} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.3">Section 4.3 of RFC 7235</a>
     */
    public static final String PROXY_AUTHENTICATE = "Proxy-Authenticate";
    /*
     * The HTTP {@code Proxy-Authorization} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.4">Section 4.4 of RFC 7235</a>
     */
    public static final String PROXY_AUTHORIZATION = "Proxy-Authorization";
    /*
     * The HTTP {@code Range} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7233#section-3.1">Section 3.1 of RFC 7233</a>
     */
    public static final String RANGE = "Range";
    /*
     * The HTTP {@code Referer} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.2">Section 5.5.2 of RFC 7231</a>
     */
    public static final String REFERER = "Referer";
    /*
     * The HTTP {@code Retry-After} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.3">Section 7.1.3 of RFC 7231</a>
     */
    public static final String RETRY_AFTER = "Retry-After";
    /*
     * The HTTP {@code Server} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.4.2">Section 7.4.2 of RFC 7231</a>
     */
    public static final String SERVER = "Server";
    /*
     * The HTTP {@code Set-Cookie} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2109#section-4.2.2">Section 4.2.2 of RFC 2109</a>
     */
    public static final String SET_COOKIE = "Set-Cookie";
    /*
     * The HTTP {@code Set-Cookie2} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc2965">RFC 2965</a>
     */
    public static final String SET_COOKIE2 = "Set-Cookie2";
    /*
     * The HTTP {@code TE} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.3">Section 4.3 of RFC 7230</a>
     */
    public static final String TE = "TE";
    /*
     * The HTTP {@code Trailer} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-4.4">Section 4.4 of RFC 7230</a>
     */
    public static final String TRAILER = "Trailer";
    /*
     * The HTTP {@code Transfer-Encoding} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-3.3.1">Section 3.3.1 of RFC 7230</a>
     */
    public static final String TRANSFER_ENCODING = "Transfer-Encoding";
    /*
     * The HTTP {@code Upgrade} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-6.7">Section 6.7 of RFC 7230</a>
     */
    public static final String UPGRADE = "Upgrade";
    /*
     * The HTTP {@code User-Agent} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-5.5.3">Section 5.5.3 of RFC 7231</a>
     */
    public static final String USER_AGENT = "User-Agent";
    /*
     * The HTTP {@code Vary} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7231#section-7.1.4">Section 7.1.4 of RFC 7231</a>
     */
    public static final String VARY = "Vary";
    /*
     * The HTTP {@code Via} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7230#section-5.7.1">Section 5.7.1 of RFC 7230</a>
     */
    public static final String VIA = "Via";
    /*
     * The HTTP {@code Warning} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7234#section-5.5">Section 5.5 of RFC 7234</a>
     */
    public static final String WARNING = "Warning";
    /*
     * The HTTP {@code WWW-Authenticate} header field name.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7235#section-4.1">Section 4.1 of RFC 7235</a>
     */
    public static final String WWW_AUTHENTICATE = "WWW-Authenticate";


    public static final HttpHeaders EMPTY = new HttpHeaders(new LinkedHashMap<String, List<String>>(), true);


    private static final Pattern ETAG_HEADER_VALUE_PATTERN = Pattern.compile("\\*|\\s*((W\\/)?(\"[^\"]*\"))\\s*,?");

    private static final DecimalFormatSymbols DECIMAL_FORMAT_SYMBOLS = new DecimalFormatSymbols(Locale.ENGLISH);


    private final Map<String, List<String>> headers;

    private final boolean readOnly;


    public HttpHeaders() {
        this(new LinkedCaseInsensitiveMap<List<String>>(8, Locale.ENGLISH), false);
    }


    private HttpHeaders(Map<String, List<String>> headers, boolean readOnly) {
        if (readOnly) {
            Map<String, List<String>> map = new LinkedCaseInsensitiveMap<List<String>>(headers.size(), Locale.ENGLISH);

            for (String key : map.keySet()) {
                List<String> valueList = map.get(key);
                map.put(key, Collections.unmodifiableList(valueList));
            }
            this.headers = Collections.unmodifiableMap(map);
        } else {
            this.headers = headers;
        }
        this.readOnly = readOnly;
    }


    public void setAccept(List<MediaType> acceptableMediaTypes) {
        set(ACCEPT, MediaType.toString(acceptableMediaTypes));
    }


    public List<MediaType> getAccept() {
        return MediaType.parseMediaTypes(get(ACCEPT));
    }

    public void setAccessControlAllowCredentials(boolean allowCredentials) {
        set(ACCESS_CONTROL_ALLOW_CREDENTIALS, Boolean.toString(allowCredentials));
    }

    public boolean getAccessControlAllowCredentials() {
        return Boolean.parseBoolean(getFirst(ACCESS_CONTROL_ALLOW_CREDENTIALS));
    }

    public void setAccessControlAllowHeaders(List<String> allowedHeaders) {
        set(ACCESS_CONTROL_ALLOW_HEADERS, toCommaDelimitedString(allowedHeaders));
    }


    public List<String> getAccessControlAllowHeaders() {
        return getValuesAsList(ACCESS_CONTROL_ALLOW_HEADERS);
    }


    public void setAccessControlAllowMethods(List<HttpMethod> allowedMethods) {
        set(ACCESS_CONTROL_ALLOW_METHODS, StringUtils.collectionToCommaDelimitedString(allowedMethods));
    }


    public List<HttpMethod> getAccessControlAllowMethods() {
        List<HttpMethod> result = new ArrayList<HttpMethod>();
        String value = getFirst(ACCESS_CONTROL_ALLOW_METHODS);
        if (value != null) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            for (String token : tokens) {
                HttpMethod resolved = HttpMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }
        }
        return result;
    }


    public void setAccessControlAllowOrigin(String allowedOrigin) {
        set(ACCESS_CONTROL_ALLOW_ORIGIN, allowedOrigin);
    }

    public String getAccessControlAllowOrigin() {
        return getFieldValues(ACCESS_CONTROL_ALLOW_ORIGIN);
    }


    public void setAccessControlExposeHeaders(List<String> exposedHeaders) {
        set(ACCESS_CONTROL_EXPOSE_HEADERS, toCommaDelimitedString(exposedHeaders));
    }

    public List<String> getAccessControlExposeHeaders() {
        return getValuesAsList(ACCESS_CONTROL_EXPOSE_HEADERS);
    }


    public void setAccessControlMaxAge(long maxAge) {
        set(ACCESS_CONTROL_MAX_AGE, Long.toString(maxAge));
    }


    public long getAccessControlMaxAge() {
        String value = getFirst(ACCESS_CONTROL_MAX_AGE);
        return (value != null ? Long.parseLong(value) : -1);
    }


    public void setAccessControlRequestHeaders(List<String> requestHeaders) {
        set(ACCESS_CONTROL_REQUEST_HEADERS, toCommaDelimitedString(requestHeaders));
    }


    public List<String> getAccessControlRequestHeaders() {
        return getValuesAsList(ACCESS_CONTROL_REQUEST_HEADERS);
    }


    public void setAccessControlRequestMethod(HttpMethod requestMethod) {
        set(ACCESS_CONTROL_REQUEST_METHOD, (requestMethod != null ? requestMethod.name() : null));
    }


    public HttpMethod getAccessControlRequestMethod() {
        return HttpMethod.resolve(getFirst(ACCESS_CONTROL_REQUEST_METHOD));
    }

    public void setAcceptCharset(List<Charset> acceptableCharsets) {
        StringBuilder builder = new StringBuilder();
        for (Iterator<Charset> iterator = acceptableCharsets.iterator(); iterator.hasNext(); ) {
            Charset charset = iterator.next();
            builder.append(charset.name().toLowerCase(Locale.ENGLISH));
            if (iterator.hasNext()) {
                builder.append(", ");
            }
        }
        set(ACCEPT_CHARSET, builder.toString());
    }

    public List<Charset> getAcceptCharset() {
        String value = getFirst(ACCEPT_CHARSET);
        if (value != null) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            List<Charset> result = new ArrayList<Charset>(tokens.length);
            for (String token : tokens) {
                int paramIdx = token.indexOf(';');
                String charsetName;
                if (paramIdx == -1) {
                    charsetName = token;
                } else {
                    charsetName = token.substring(0, paramIdx);
                }
                if (!charsetName.equals("*")) {
                    result.add(Charset.forName(charsetName));
                }
            }
            return result;
        } else {
            return Collections.emptyList();
        }
    }

    public void setAllow(Set<HttpMethod> allowedMethods) {
        set(ALLOW, StringUtils.collectionToCommaDelimitedString(allowedMethods));
    }

    public Set<HttpMethod> getAllow() {
        String value = getFirst(ALLOW);
        if (StringUtils.hasLength(value)) {
            String[] tokens = StringUtils.tokenizeToStringArray(value, ",");
            List<HttpMethod> result = new ArrayList<HttpMethod>(tokens.length);
            for (String token : tokens) {
                HttpMethod resolved = HttpMethod.resolve(token);
                if (resolved != null) {
                    result.add(resolved);
                }
            }
            return EnumSet.copyOf(result);
        } else {
            return EnumSet.noneOf(HttpMethod.class);
        }
    }


    public void setCacheControl(String cacheControl) {
        set(CACHE_CONTROL, cacheControl);
    }


    public String getCacheControl() {
        return getFieldValues(CACHE_CONTROL);
    }

    public void setConnection(String connection) {
        set(CONNECTION, connection);
    }


    public void setConnection(List<String> connection) {
        set(CONNECTION, toCommaDelimitedString(connection));
    }

    /*
     * Return the value of the {@code Connection} header.
     */
    public List<String> getConnection() {
        return getValuesAsList(CONNECTION);
    }


    public Locale getContentLanguage() {

        List<String> valuesAsList = getValuesAsList(CONTENT_LANGUAGE);

        String s = valuesAsList.get(0);
        return s == null ? null : new Locale(s);
    }

    public void setContentLength(long contentLength) {
        set(CONTENT_LENGTH, Long.toString(contentLength));
    }


    public long getContentLength() {
        String value = getFirst(CONTENT_LENGTH);
        return (value != null ? Long.parseLong(value) : -1);
    }

    public void setContentType(MediaType mediaType) {
        if (mediaType != null) {
            Assert.isTrue(!mediaType.isWildcardType(), "Content-Type cannot contain wildcard type '*'");
            Assert.isTrue(!mediaType.isWildcardSubtype(), "Content-Type cannot contain wildcard subtype '*'");
            set(CONTENT_TYPE, mediaType.toString());
        } else {
            set(CONTENT_TYPE, null);
        }
    }

    public MediaType getContentType() {
        String value = getFirst(CONTENT_TYPE);
        return (StringUtils.hasLength(value) ? MediaType.parseMediaType(value) : null);
    }






    public void setETag(String etag) {
        if (etag != null) {
            Assert.isTrue(etag.startsWith("\"") || etag.startsWith("W/"),
                    "Invalid ETag: does not start with W/ or \"");
            Assert.isTrue(etag.endsWith("\""), "Invalid ETag: does not end with \"");
        }
        set(ETAG, etag);
    }

    public String getETag() {
        return getFirst(ETAG);
    }





    public void setHost(InetSocketAddress host) {
        if (host != null) {

            InetAddress address = host.getAddress();
            String hostAddress = address.getHostAddress();
            int port = host.getPort();
            if (port != 0) {
                hostAddress = hostAddress + ":" + port;
            }
            set(HOST, hostAddress);
        } else {
            set(HOST, null);
        }
    }


    public InetSocketAddress getHost() {
        String value = getFirst(HOST);
        if (value == null) {
            return null;
        }

        String host = null;
        int port = 0;
        int separator = (value.startsWith("[") ? value.indexOf(':', value.indexOf(']')) : value.lastIndexOf(':'));
        if (separator != -1) {
            host = value.substring(0, separator);
            String portString = value.substring(separator + 1);
            try {
                port = Integer.parseInt(portString);
            } catch (NumberFormatException ex) {
                // ignore
            }
        }

        if (host == null) {
            host = value;
        }
        return InetSocketAddress.createUnresolved(host, port);
    }

    public void setIfMatch(String ifMatch) {
        set(IF_MATCH, ifMatch);
    }

    public void setIfMatch(List<String> ifMatchList) {
        set(IF_MATCH, toCommaDelimitedString(ifMatchList));
    }

    public List<String> getIfMatch() {
        return getETagValuesAsList(IF_MATCH);
    }





    public void setIfNoneMatch(String ifNoneMatch) {
        set(IF_NONE_MATCH, ifNoneMatch);
    }


    public void setIfNoneMatch(List<String> ifNoneMatchList) {
        set(IF_NONE_MATCH, toCommaDelimitedString(ifNoneMatchList));
    }


    public List<String> getIfNoneMatch() {
        return getETagValuesAsList(IF_NONE_MATCH);
    }





    public void setLocation(URI location) {
        set(LOCATION, (location != null ? location.toASCIIString() : null));
    }

    public URI getLocation() {
        String value = getFirst(LOCATION);
        return (value != null ? URI.create(value) : null);
    }


    public void setOrigin(String origin) {
        set(ORIGIN, origin);
    }


    public String getOrigin() {
        return getFirst(ORIGIN);
    }

    /*
     * Set the (new) value of the {@code Pragma} header.
     */
    public void setPragma(String pragma) {
        set(PRAGMA, pragma);
    }

    /*
     * Return the value of the {@code Pragma} header.
     */

    public String getPragma() {
        return getFirst(PRAGMA);
    }



    /*
     * Set the (new) value of the {@code Upgrade} header.
     */
    public void setUpgrade(String upgrade) {
        set(UPGRADE, upgrade);
    }

    /*
     * Return the value of the {@code Upgrade} header.
     */

    public String getUpgrade() {
        return getFirst(UPGRADE);
    }

    /*
     * Set the request header names (e.g. "Accept-Language") for which the
     * response is subject to content negotiation and variances based on the
     * value of those request headers.
     *
     * @param requestHeaders the request header names
     * @since 4.3
     */
    public void setVary(List<String> requestHeaders) {
        set(VARY, toCommaDelimitedString(requestHeaders));
    }

    /*
     * Return the request header names subject to content negotiation.
     *
     * @since 4.3
     */
    public List<String> getVary() {
        return getValuesAsList(VARY);
    }









    public List<String> getValuesAsList(String headerName) {
        List<String> values = get(headerName);
        if (values != null) {
            List<String> result = new ArrayList<String>();
            for (String value : values) {
                if (value != null) {
                    Collections.addAll(result, StringUtils.tokenizeToStringArray(value, ","));
                }
            }
            return result;
        }
        return Collections.emptyList();
    }


    protected List<String> getETagValuesAsList(String headerName) {
        List<String> values = get(headerName);
        if (values != null) {
            List<String> result = new ArrayList<String>();
            for (String value : values) {
                if (value != null) {
                    Matcher matcher = ETAG_HEADER_VALUE_PATTERN.matcher(value);
                    while (matcher.find()) {
                        if ("*".equals(matcher.group())) {
                            result.add(matcher.group());
                        } else {
                            result.add(matcher.group(1));
                        }
                    }
                    if (result.isEmpty()) {
                        throw new IllegalArgumentException(
                                "Could not parse header '" + headerName + "' with value '" + value + "'");
                    }
                }
            }
            return result;
        }
        return Collections.emptyList();
    }



    protected String getFieldValues(String headerName) {
        List<String> headerValues = get(headerName);
        return (headerValues != null ? toCommaDelimitedString(headerValues) : null);
    }

    protected String toCommaDelimitedString(List<String> headerValues) {
        StringBuilder builder = new StringBuilder();
        for (Iterator<String> it = headerValues.iterator(); it.hasNext(); ) {
            String val = it.next();
            builder.append(val);
            if (it.hasNext()) {
                builder.append(", ");
            }
        }
        return builder.toString();
    }



    @Override
    public String getFirst(String headerName) {
        List<String> headerValues = this.headers.get(headerName);
        return (headerValues != null ? headerValues.get(0) : null);
    }


    @Override
    public void add(String headerName, String headerValue) {

        List<String> headerValues =   this.headers.containsKey(headerName)? this.headers.get(headerName)
                : new LinkedList<String>();
        headerValues.add(headerValue);
    }

    @Override
    public void addAll(String key, List<? extends String> values) {


        List<String> currentValues =   this.headers.containsKey(key)? this.headers.get(key)
                : new LinkedList<String>();
        currentValues.addAll(values);
    }

    @Override
    public void addAll(MultiValueMap<String, String> values) {

        for (String key : values.keySet()) {
            this.addAll(key,values.get(key));
        }
    }


    @Override
    public void set(String headerName, String headerValue) {
        List<String> headerValues = new LinkedList<String>();
        headerValues.add(headerValue);
        this.headers.put(headerName, headerValues);
    }

    @Override
    public void setAll(Map<String, String> values) {

        for (String key : values.keySet()) {
            this.set(key,values.get(key));
        }
    }

    @Override
    public Map<String, String> toSingleValueMap() {
        LinkedHashMap<String, String> singleValueMap = new LinkedHashMap<String, String>(this.headers.size());

        for (String header : this.headers.keySet()) {
            List<String> valueList = this.headers.get(header);
            singleValueMap.put(header, valueList.get(0));
        }
        return singleValueMap;
    }


    // Map implementation

    @Override
    public int size() {
        return this.headers.size();
    }

    @Override
    public boolean isEmpty() {
        return this.headers.isEmpty();
    }

    @Override
    public boolean containsKey(Object key) {
        return this.headers.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value) {
        return this.headers.containsValue(value);
    }

    @Override

    public List<String> get(Object key) {
        return this.headers.get(key);
    }

    @Override
    public List<String> put(String key, List<String> value) {
        return this.headers.put(key, value);
    }

    @Override
    public List<String> remove(Object key) {
        return this.headers.remove(key);
    }

    @Override
    public void putAll(Map<? extends String, ? extends List<String>> map) {
        this.headers.putAll(map);
    }

    @Override
    public void clear() {
        this.headers.clear();
    }

    @Override
    public Set<String> keySet() {
        return this.headers.keySet();
    }

    @Override
    public Collection<List<String>> values() {
        return this.headers.values();
    }

    @Override
    public Set<Entry<String, List<String>>> entrySet() {
        return this.headers.entrySet();
    }


    @Override
    public boolean equals(Object other) {
        if (this == other) {
            return true;
        }
        if (!(other instanceof HttpHeaders)) {
            return false;
        }
        HttpHeaders otherHeaders = (HttpHeaders) other;
        return this.headers.equals(otherHeaders.headers);
    }

    @Override
    public int hashCode() {
        return this.headers.hashCode();
    }

    @Override
    public String toString() {
        return this.headers.toString();
    }


    /*
     * Return an {@code HttpHeaders} object that can only be read, not written to.
     */
    public static HttpHeaders readOnlyHttpHeaders(HttpHeaders headers) {
        Assert.notNull(headers, "HttpHeaders must not be null");
        return (headers.readOnly ? headers : new HttpHeaders(headers, true));
    }



}
